package org.urchr.todoist.lib;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import java.io.*;
import java.util.*;
import javax.microedition.rms.*;

/**
 *
 * @author Kostya
 */
public class Todoist {
    public static final String TODO_URL = "http://todoist.com/API/";

    public static Todoist getInstance() {
        if(instance==null)
            instance = new Todoist();
        return instance;
    }
    private String token = "";
    private Vector projects = new Vector();
    private Vector orders = new Vector();
    private Vector topics = new Vector();

    private RecordStore _rmsProjacts;
    private RecordStore _rmsTasks;
    
    private Todoist(){
        try {
            _rmsProjacts = RecordStore.openRecordStore("PROJECTS", true);
            _rmsTasks = RecordStore.openRecordStore("TASKS", true);
        }
        catch (Exception ex) {}
    }
    
    private Todoist(String token){
        this.token = token;

        try {
            _rmsProjacts = RecordStore.openRecordStore("PROJECTS", true);
            _rmsTasks = RecordStore.openRecordStore("TASKS", true);
        }
        catch (Exception ex) {}
    }
    
    private Vector fetchURL(String URL) throws IOException{
        return fetchURL(URL, false);
    }
    public Vector fetchURL(String URL, boolean print) throws IOException{
        try {
            JSONParser p = new JSONParser("");

            HttpConnection conn = (HttpConnection) Connector.open(URL);
            int rc = conn.getResponseCode();
            if (rc != HttpConnection.HTTP_OK) {
                throw new IOException("HTTP response code: " + rc);
            }

            InputStreamReader d = new InputStreamReader(conn.openInputStream(), "utf-8");

            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = d.read()) != -1) sb.append((char)ch);
            String res = sb.toString();
            /*int data = -1;
            String res = "";
            while ((data = d.read()) != -1) {
                res += (char)data;
            }*/
            d.close();
            conn.close();

            p.setData(res);
            Vector v = p.parseData();
            
            //if(print)p.printVector(v, 0);

            return v;

        } catch (IOException iOException) {
            iOException.printStackTrace();
            throw new IOException(iOException.getMessage());
        }
    }
    
    private int processProject(int index, Vector data, Project pr, int indent) throws IOException{
        Hashtable h = (Hashtable) data.elementAt(index);
        pr.setColor(h.get("color").toString());
        pr.setId(h.get("id").toString());
        pr.setIndent(h.get("indent").toString());
        pr.setName(h.get("name").toString());
        pr.setCacheCount(h.get("cache_count").toString());
        pr.setCollapsed("1".equals(h.get("collapsed")));
        if(!pr.getClearName().startsWith("~")){
            Vector tasks = fetchURL(TODO_URL+"getUncompletedItems?project_id="+
                    pr.getId()+"&token="+token, true);

            for (int i = 0; i < tasks.size(); i++) {
                h = (Hashtable) tasks.elementAt(i);
                Task t = new Task(pr, null);
                i = processTask(i, tasks, t, Integer.parseInt(h.get("indent").toString())+1);
    //            if(!t.isChecked())
                pr.addTask(t);
            }
        }

        this.SaveProject(pr);
        
        for (int i = index+1; i < data.size(); i++) {
            h = (Hashtable) data.elementAt(i);
            if(Integer.parseInt(h.get("indent").toString())<indent)
                return i-1;

            Project p = new Project();
            p.setColor(h.get("color").toString());
            p.setId(h.get("id").toString());
            p.setIndent(h.get("indent").toString());
            p.setName(h.get("name").toString());
            pr.addSubProject(p);

            i = processProject(i, data, p, Integer.parseInt(h.get("indent").toString())+1);

            //this.SaveProject(p);
        }

        //this.SaveProject(pr);

        return data.size();
    }
    
    private int processTask(int index, Vector data, Task t, int indent) throws IOException{
        Hashtable h = (Hashtable) data.elementAt(index);
        t.setId(h.get("id").toString());
        t.setIndent(h.get("indent").toString());
        t.setName(h.get("content").toString());
        t.setChecked(h.get("checked").toString());
        t.setDate(h.get("date_string").toString());
        t.setWrongPriority(h.get("priority").toString());
        t.setCollapsed("1".equals(h.get("collapsed")));
        for (int i = index+1; i < data.size(); i++) {
            h = (Hashtable) data.elementAt(i);
            if(Integer.parseInt(h.get("indent").toString())<indent)
                return i-1;
            Task st = new Task(t.getProject(), t);
            st.setIndent(h.get("indent").toString());
            i = processTask(i, data, st, st.getIndent()+1);
//            if(!st.isChecked())
            t.addSubTask(st);
        }
        return data.size();
    }

    public boolean LoadProjects()
    {
        int recNum = 0;
        Vector vTasks = null;
        Vector vProjects = null;

        try
        {
            RecordEnumeration reTask = _rmsTasks.enumerateRecords(null, null, false);

            if (reTask.numRecords() > 0)
            {
                vTasks = new Vector(reTask.numRecords());

                while (reTask.hasNextElement())
                {
                    Task t = new Task(null, null);
                    
                    t.setRmsID(reTask.nextRecordId());

                    byte[] record = _rmsTasks.getRecord(t.getRmsID());

                    ByteArrayInputStream bais = new ByteArrayInputStream(record);
                    DataInputStream dis = new DataInputStream(bais);

                    t.setId(dis.readUTF());
                    t.setProjectId(dis.readUTF());
                    t.setName(dis.readUTF());
                    t.setDate(dis.readUTF());
                    t.setIndent(dis.readInt());
                    t.setPriority(dis.readInt());
                    t.setIndent(dis.readInt());
                    t.setChecked(dis.readBoolean());

                    t.setOrderAction(dis.readInt());
                    t.setParentTaskID(dis.readUTF());

                    dis.close();
                    bais.close();

                    if (t.getOrderAction() != Order.NONE) {
                        orders.addElement(new Order(t.getOrderAction(), t));
                    }

                    if (t.getOrderAction() != Order.DELETE_TASK) {
                        vTasks.addElement(t);
                    }
                }

                for (int i1=0; i1<vTasks.size(); i1++)
                {
                    Task t1 = (Task)vTasks.elementAt(i1);
                    if (t1.getParentTask() == null) continue;

                    for (int i2=0; i2<vTasks.size(); i2++)
                    {
                        Task t2 = (Task)vTasks.elementAt(i2);

                        if (t1.getParentTaskID().equals(t2.getId())) {
                            t2.addSubTask(t1);
                            t1.setParentTask(t2);
                            vTasks.removeElement(t1);
                            break;
                        }
                    }
                }

                DashboardTopic topic = new DashboardTopic();
                topic.setName("Today:");
                topics.addElement(topic);
                
                for (int i=vTasks.size()-1; i>=0; i--)
                {
                    Task t = (Task)vTasks.elementAt(i);
                    if (t.getDate() == null) continue;

                    String day = "";
                    for (int d=0; d<t.getDate().length(); d++)
                    {
                        char ch = t.getDate().charAt(d);
                        if (ch == '0' || ch == '1' || ch == '2' || ch == '3' || ch == '4' ||
                            ch == '5' || ch == '6' || ch == '7' || ch == '8' || ch == '9')
                        {
                            day += ch;
                        }
                        else if (!day.equals("")) {
                            break;
                        }
                    }

                    if (!day.equals(""))
                    {
                        int iDay = Integer.parseInt(day);
                        int currentDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);

                        if (iDay == currentDay) {
                            topic.addTask(t);
                        }
                    }
                }
            }

            RecordEnumeration re = _rmsProjacts.enumerateRecords(null, null, false);

            if (re.numRecords() > 0)
            {
                vProjects = new Vector(re.numRecords());

                while (re.hasNextElement())
                {
                    Project p = new Project();
                    vProjects.addElement(p);

                    p.setRmsID(re.nextRecordId());

                    byte[] record = _rmsProjacts.getRecord(p.getRmsID());

                    ByteArrayInputStream bais = new ByteArrayInputStream(record);
                    DataInputStream dis = new DataInputStream(bais);

                    p.setId(dis.readUTF());
                    p.setName(dis.readUTF());
                    p.setColor(dis.readUTF());
                    p.setOrder(dis.readUTF());
                    p.setNIndent(dis.readInt());
                    p.setParentId(dis.readUTF());

                    dis.close();
                    bais.close();

                    for (int i=vTasks.size()-1; i>=0; i--)
                    {
                        Task t = (Task)vTasks.elementAt(i);
                        
                        if (t.getProjectId().equals(p.getId())) {
                            t.setProject(p);
                            p.addTask(t);
                        }
                    }
                }

                for (int j=vProjects.size()-1; j>=0; j--) {
                    projects.addElement(vProjects.elementAt(j));
                }

                for (int j1=0; j1<projects.size(); j1++)
                {
                    Project p1 = (Project)projects.elementAt(j1);
                    if (p1.getParentId().equals("")) continue;

                    for (int j2=0; j2<projects.size(); j2++)
                    {
                        Project p2 = (Project)projects.elementAt(j2);

                        if (!p1.getParentId().equals("") && p1.getParentId().equals(p2.getId())) {
                            p2.addSubProject(p1);
                            projects.removeElement(p1);
                            break;
                        }
                    }
                }
            }
        }
        catch (Exception ioe) {
            return false;
        }

        return true;
    }

    public void SaveTask(Task t)
    {
        ByteArrayOutputStream tBaos = new ByteArrayOutputStream();
        DataOutputStream tDos = new DataOutputStream(tBaos);

        try
        {
            tDos.writeUTF(t.getId());
            tDos.writeUTF(t.getProjectId());
            tDos.writeUTF(t.getName());
            if (t.getDate() != null) tDos.writeUTF(t.getDate()); else tDos.writeUTF("");
            tDos.writeInt(t.getIndent());
            tDos.writeInt(t.getPriority());
            tDos.writeInt(t.getIndent());
            tDos.writeBoolean(t.isChecked());

            tDos.writeInt(t.getOrderAction());
            tDos.writeUTF(t.getParentTaskID());

            t.setRmsID( _rmsTasks.addRecord(tBaos.toByteArray(), 0, tBaos.toByteArray().length) );
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally
        {
            try {
                tDos.close();
                tBaos.close();
            } catch (Exception ex) {}
        }
    }

    public void ChangeTask(Task t)
    {
        try
        {
            _rmsTasks.deleteRecord(t.getRmsID());
            this.SaveTask(t);
        }
        catch (RecordStoreException ex) {
            ex.printStackTrace();
        }
    }

    public void DeleteTask(Task t)
    {
        try {
            _rmsTasks.deleteRecord(t.getRmsID());
        }
        catch (RecordStoreException ex) {
            ex.printStackTrace();
        }
    }

    private void SaveProject(Project p)
    {
        ByteArrayOutputStream prjBaos = new ByteArrayOutputStream();
        DataOutputStream prjDos = new DataOutputStream(prjBaos);

        try
        {
            prjDos.writeUTF(p.getId());
            prjDos.writeUTF(p.getName());
            prjDos.writeUTF(p.getColor());
            prjDos.writeUTF(p.getOrder());
            prjDos.writeInt(p.getNIndent());
            prjDos.writeUTF(p.getParentId());

            for (int i=0; i<p.getTasksCount(); i++)
            {
                Task t = p.getTaskAt(i);
                this.SaveTask(t);

                for (int j=0; j<t.getSubTasksCount(); j++)
                {
                    Task subTask = t.getSubTaskAt(j);
                    this.SaveTask(subTask);
                }
            }

            p.setRmsID( _rmsProjacts.addRecord(prjBaos.toByteArray(), 0, prjBaos.toByteArray().length) );
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally
        {
            try {
                prjDos.close();
                prjBaos.close();
            } catch (Exception ex) {}
        }
    }
    
    public boolean doRefreshData()
    {
        try {
            //RecordStore.deleteRecordStore("PROJECTS");
            //RecordStore.deleteRecordStore("TASKS");

            RecordEnumeration re = _rmsProjacts.enumerateRecords(null, null, false);
            if (re.numRecords() > 0) {
                while (re.hasNextElement()) _rmsProjacts.deleteRecord(re.nextRecordId());
            }

            RecordEnumeration reTask = _rmsTasks.enumerateRecords(null, null, false);
            if (reTask.numRecords() > 0) {
                while (reTask.hasNextElement()) _rmsTasks.deleteRecord(reTask.nextRecordId());
            }
        }
        catch (Exception e) {
            //e.printStackTrace();
        }

        projects.removeAllElements();
        orders.removeAllElements();

        try
        {
            Vector v = fetchURL(TODO_URL + "getProjects?token=" + token);
            for (int i = 0; i < v.size(); i++) {
                Hashtable h = (Hashtable) v.elementAt(i);
                Project p = new Project();
                i = processProject(i, v, p, Integer.parseInt(h.get("indent").toString()) + 1);
                projects.addElement(p);

                //this.SaveProject(p);
            }

            /*for (int i = 0; i < projects.size(); i++) {
                Project pr = (Project) projects.elementAt(i);
                pr.printProject();
            }*/
        }
        catch (IOException ex) {
            return false;
        }
        return true;
    }
    
    private static Todoist instance = null;
    
    public int getProjectsCount(){
        return projects.size();
    }
    
    public Project getProjectAt(int index){
        return (Project) projects.elementAt(index);
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
    
    public void addOrder(Order o){
        orders.addElement(o);
    }
    
    public int getOrdersCount(){
        return orders.size();
    }
    
    public int getTopicsCount(){
        return topics.size();
    }
    
    public DashboardTopic getTopicAt(int i){
        return (DashboardTopic) topics.elementAt(i);
    }
    
    public Task getTaskById(String id){
        for (int i = 0; i < getProjectsCount(); i++) {
            Project p = getProjectAt(i);
            Task t = p.searchTask(id);
            if(t!=null)
                return t;
        }
        return null;
    }
    
    public boolean commitOrders(){
        int ordersSize = orders.size();
        for (int idx = 0; idx < ordersSize; idx++) {
            try {
                Order o = (Order) orders.elementAt(0);
                o.executeOrder(this);
                orders.removeElement(o);
            } catch (IOException ex) {
                ex.printStackTrace();
                return false;
            }
        }
        return true;
    }
    
    private String dateToISOString(Calendar c){
        String res = "";
        res = c.get(Calendar.YEAR)+"-"+(c.get(Calendar.MONTH)+1)+"-"+c.get(Calendar.DAY_OF_MONTH)+"T00:00";
        return res;
    }
    
    private String dateToString(Calendar c){
        String res = "";
        res = MONTHS[c.get(Calendar.MONTH)]+" "+c.get(Calendar.DAY_OF_MONTH);
        return res;
    }
    
    private static int[] DAYS_IN_MONTHS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    private static String[] MONTHS = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    
    private Calendar addDays(Calendar c, int days){
        int newDay = c.get(Calendar.DAY_OF_MONTH)+days;
        int month = c.get(Calendar.MONTH);
        int year = c.get(Calendar.YEAR);
        while(DAYS_IN_MONTHS[month]<newDay){
            newDay = newDay-DAYS_IN_MONTHS[month];
            month++;
            if(month>11){
                month = 0;
                year++;
            }
        }
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, month);
        c.set(Calendar.DAY_OF_MONTH, newDay);
        return c;
    }
    
    public boolean makeQuery(String query){
        try {
            topics.removeAllElements();
            Hashtable captions = new Hashtable();
            JSONParser p = new JSONParser(query);
            Vector data = p.parseData();
            Vector queries = new Vector();
            Calendar c = Calendar.getInstance();
            for (int i = 0; i < data.size(); i++) {
                String q = ((String) data.elementAt(i)).toLowerCase();
                System.out.println("Query part = "+q);
                if(q.equals("tod") || q.equals("today")){
                    c.setTime(new Date());
                    queries.addElement(dateToISOString(c));
                    captions.put(dateToISOString(c), "Today");
                }
                if(q.equals("tom") || q.equals("tomorrow")){
                    c.setTime(new Date());
                    c = addDays(c, 1);
                    queries.addElement(dateToISOString(c));
                    captions.put(dateToISOString(c), "Tomorrow");
                }
                if(q.equals("od") || q.equals("overdue")){
                    queries.addElement("overdue");
                }
                if(q.equals("p1") || q.equals("p2") || q.equals("p3")){
                    queries.addElement(q);
                }
                if(q.endsWith("days") && q.indexOf(" ")!=-1){
                    try {
                        int days = Integer.parseInt(q.substring(0, q.indexOf(" ")));
                        for (int j = 0; j < days; j++) {
                            c.setTime(new Date());
                            addDays(c, j);
                            queries.addElement(dateToISOString(c));                            
                            if(j==0)
                                captions.put(dateToISOString(c), "Today");
                            else
                                if(j==1)
                                    captions.put(dateToISOString(c), "Tomorrow");
                                else
                                    captions.put(dateToISOString(c), dateToString(c));
                        }
                    } catch (NumberFormatException ex) {
                    }
                }
            }
            String result = "";
            for (int i = 0; i < queries.size(); i++) {
                if(i>0)
                    result += ",";
                result += ("\""+queries.elementAt(i)+"\"");
            }
            System.out.println("Result is "+result);
            Vector res = fetchURL(TODO_URL + "query?queries=%5B" + encode(result) + "%5D&token=" + getToken(), true);
            for (int i = 0; i < res.size(); i++) {
                Hashtable h = (Hashtable) res.elementAt(i);
                DashboardTopic topic = new DashboardTopic();
                if("overdue".equals(h.get("type")))
                    topic.setName("Overdue tasks:");
                if("priority".equals(h.get("type"))){
                    if("p1".equals(h.get("query")))
                        topic.setName("Priority 1:");
                    if("p2".equals(h.get("query")))
                        topic.setName("Priority 2:");
                    if("p3".equals(h.get("query")))
                        topic.setName("Priority 3:");
                }
                if("date".equals(h.get("type")))
                    topic.setName(captions.get(h.get("query"))+":");
                Vector tasks = (Vector) h.get("data");
                if(tasks.size()>0){
                    for (int j = 0; j < tasks.size(); j++) {
                        Hashtable th = (Hashtable) tasks.elementAt(j);
                        String id = th.get("id").toString();
                        Task t = getTaskById(id);
                        if(t==null){
                            t = new Task(null, null);
                            t.setId(id);
                        }
                        t.setIndent(th.get("indent").toString());
                        t.setName(th.get("content").toString());
                        t.setChecked(th.get("checked").toString());
                        t.setDate(th.get("date_string").toString());
                        t.setWrongPriority(th.get("priority").toString());
                        topic.addTask(t);
                    }
                    topics.addElement(topic);
                }
            }
            return true;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return false;
    }
    
    public static String encode(String s){
        return URLUTF8Encoder.encode(s);
    }
}
